﻿using System;
using System.Collections.Generic;
using System.Text;
using log4net.Core;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using log4net.Util;


namespace Splunk.Extensions
{
    /// <summary>
    /// TCP based appender with reconnect support
    /// </summary>
    public class SplunkTCPAppender : BaseDNSAppender
    {
        /// <summary>
        /// .ctor
        /// </summary>
        public SplunkTCPAppender()
            : base(LoadBalanceMode.Persistent)
        {

        }

        private Thread _SenderThread;
        private AutoResetEvent _SendEvent = new AutoResetEvent(false);
        private Queue<byte[]> _MessageQueue = new Queue<byte[]>(MAXQUEUED);

        const int MAXBUFFER = 4096;

        void mainSendLoop()
        {
            try
            {
                Dictionary<IPEndPoint, Socket> ConnectedHosts = new Dictionary<IPEndPoint, Socket>();
                
                while (true)
                {
                    byte[] message;
                    lock (_MessageQueue)
                    {
                        if (_MessageQueue.Count == 0)
                            goto QueueEmpty;
                        message = _MessageQueue.Dequeue();
                    }

                    IPEndPoint remoteAddress = GetEndPoint();
                    Socket socket = null;

                    try
                    {
                        if (!ConnectedHosts.TryGetValue(remoteAddress, out socket))
                        {
                            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                            socket.SendTimeout = 3000;
                            socket.SendBufferSize = MAXBUFFER;
                            if (LogLog.IsDebugEnabled) LogLog.Debug(
                                string.Format("SplunkTCPAppender:Connecting to {0}...", remoteAddress)
                                );
                            socket.Connect(remoteAddress);
                            ConnectedHosts.Add(remoteAddress, socket);
                        }
                    }
                    catch (Exception ex)
                    {
                        MarkEndPointFailure(remoteAddress);
                        SendMessage(message);
                        if (LogLog.IsErrorEnabled) LogLog.Error("SplunkTCPAppender:Exception thrown connecting to host", ex);
                        goto QueueEmpty;
                    }

                    try
                    {
                        if (message.Length < MAXBUFFER)
                        {
                            socket.SendTo(message, 0, message.Length, SocketFlags.None, remoteAddress);
                        }
                        else
                        {
                            for (int i = 0; i < message.Length; i += MAXBUFFER)
                            {
                                socket.SendTo(message, i, message.Length - i, SocketFlags.None, remoteAddress);
                            }
                        }
                    }
                    catch(Exception ex)
                    {
                        if (LogLog.IsErrorEnabled) LogLog.Error(
                              string.Format("SplunkTCPAppender:Exception while sending to {0}. Removing this host from connected hosts and resending message", remoteAddress),
                              ex);
                        SendMessage(message);
                        MarkEndPointFailure(remoteAddress);

                        try
                        {
                            socket.Disconnect(false);
                        }
                        catch (Exception ex0)
                        {
                            if (LogLog.IsErrorEnabled) LogLog.Error("SplunkTCPAppender:Exception while cleaning socket.", ex0);
                        }


                        if (ConnectedHosts.ContainsKey(remoteAddress))
                        {
                            ConnectedHosts.Remove(remoteAddress);
                        }

                        if (LogLog.IsErrorEnabled) LogLog.Error("SplunkTCPAppender:Exception thrown connecting to host", ex);
                    }


                QueueEmpty:
                    if (_MessageQueue.Count == 0)
                        _SendEvent.WaitOne(5000, false);
                }
            }
            catch (ThreadAbortException)
            {

            }
        }

        /// <summary>
        /// 
        /// </summary>
        protected override void OnClose()
        {
            _SenderThread.Abort();
            base.OnClose();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        protected override bool Initialize()
        {
            _SenderThread = new Thread(new ThreadStart(mainSendLoop));
            _SenderThread.IsBackground = true;
            _SenderThread.Start();
            return true;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="message"></param>
        protected override void SendMessage(byte[] message)
        {
            lock (_MessageQueue)
            {
                _MessageQueue.Enqueue(message);
            }

            _SendEvent.Set();
        }
    }
}
